/*
Thrust scale for model aircraft with ESP 8266 and WLAN access.

ESP 8266 is configured as access point (AP) creating its own WLAN. Thus no 
local WLAN is needed e.g. on the model air field.

SSID is "ESP8266-Access-Point".
No password is needed for access.

The access point sends out a simple HTML page indicating the scale weight.
Data are also sent to a computer via USB (if connected) for test purposes.

Basic code to measure weight with HX711 and to configure ESP8266 as AP is from:

Rui Santos, Random Nerd Tutorials

Complete project details and lots of explanation can be found at:
https://randomnerdtutorials.com/esp8266-load-cell-hx711/
https://randomnerdtutorials.com/esp8266-nodemcu-access-point-ap-web-server/
  
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files.
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

08.06.2022
Knud Jacobsen
*/

// Include libraries
#include "HX711.h"

#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_I2CDevice.h>
 
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Define constants
#define BAUDRATE 9600
// #define BAUDRATE 115200
#define OLED_RESET 0

// Create objects
// Create HX711 and SSD1306 objects
HX711 loadcell;
Adafruit_SSD1306 display(OLED_RESET);

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

// Declare global constants
// HX711 circuit wiring
const int LOADCELL_DOUT_PIN = 13; // D7
const int LOADCELL_SCK_PIN = 15;  // D8

// Adjustment settings
const long LOADCELL_OFFSET = -101000;
const long LOADCELL_DIVIDER = 725000;

// SSID and password
const char* ssid     = "ESP8266-Access-Point";
// const char* password = "12345";

// Define global variables
float weight = 0.0;

// Generally, you should use "unsigned long" for variables that hold time
// The value will quickly become too large for an int to store

// Will store last time data was sent to client
unsigned long previousMillis = 0;
const long interval = 1000;

// HTML page
// Store web page to flash memory
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style>
    html {
     font-family: Arial;
     display: inline-block;
     margin: 0px auto;
     text-align: center;
    }
    h2 { font-size: 3.0rem; }
    p { font-size: 3.0rem; }
    .units { font-size: 1.2rem; }
    .schub-labels{
      font-size: 1.5rem;
      vertical-align:middle;
      padding-bottom: 15px;
    }
  </style>
</head>

<body>
  <h2>ESP8266 Schub Server</h2>
  <p>
    <span class="schub-labels">Weight</span>
    <span id="weight">%WEIGHT%</span>
    <sup class="units">kg</sup>
  </p>
</body>

<script>
setInterval(function ( ) {
  var xhttp = new XMLHttpRequest();
  xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status == 200) {
      document.getElementById("weight").innerHTML = this.responseText;
    }
  };
  xhttp.open("GET", "/weight", true);
  xhttp.send();
}, 1000 ) ;
</script>

</html>)rawliteral";

// Declaration of functions
void readSensors(float *pWeight);   // Dereferenced pointer to weight
void displaySensors(float weight);
String replaceHTML(const String &var);  // Pointer to var

// Body of functions
// Read sensors
void readSensors(float *pWeight) {
 
  // Acquire weight
  *pWeight = loadcell.get_units(10);

  // Check if any reads failed and exit early (to try again).
  if (isnan(*pWeight)) {
    Serial.println(F("Failed to read from sensor!"));
    return;
  } else {
    Serial.print("Weight ");
    Serial.println(*pWeight);
  }
}

// Display sensors values to OLED
void displaySensors(float weight) {
  display.clearDisplay();
  display.setTextSize(2);
  display.setTextColor(WHITE);
  display.setCursor(0,16);
  display.print(weight);
  display.setTextSize(1);
  display.println("kg");
  display.display();
}

// Replace HTML placeholder with sensor values
String replaceHTML(const String &var){
  // Serial.println(var);
  if(var == "WEIGHT"){
    return String(weight);
  }
  return String();
}

// Setup section
void setup() {
  Serial.begin(BAUDRATE);

  // Initialize SSD1306 library
  display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
  display.display();
  delay(2000);
  display.clearDisplay();

  // Initialize HX711 library
  loadcell.begin(LOADCELL_DOUT_PIN, LOADCELL_SCK_PIN);
  loadcell.set_scale(LOADCELL_DIVIDER);
  loadcell.set_offset(LOADCELL_OFFSET);

  Serial.println();
  Serial.println("Setting AP (Access Point)…");
  // Remove the password parameter, if you want the AP (Access Point) to be open
  WiFi.softAP(ssid);  // No password required

  // IP Address 192.168.4.1
  IPAddress IP = WiFi.softAPIP();
  Serial.print("AP IP address: ");
  Serial.println(IP);

  // Print ESP8266 Local IP Address
  Serial.print("Local IP address: ");
  Serial.println(WiFi.localIP());

  // Route for root / web page
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html, replaceHTML);
  });
  server.on("/weight", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/plain", String(weight).c_str());
  });

  // Start server
  server.begin();
}

// Main loop section
void loop() {
  // Check to see if it's time to send data to client
  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= interval) {
    // Save the last time we sent data to client
    previousMillis = currentMillis;

    readSensors(&weight);   // Pointer to weight
    displaySensors(weight);
  }
}